<?php
/**
 * Geneone: Content Management Framework
 * Discussion Object
 *
 * http://geneone.neondragon.net/
 *
 * @package Geneone
 * @author Khlo
 * @version 1.0
 * @copyright 2004-2006 Geneone Development Team
 */

class Gene_Object_Model_Discussion extends Gene_Object_Model {
	/**
	 * Extra RDF Namespaces required.
	 */
	var $rdfNamespaces = array();
	
	/**
	 * Default column ordering.
	 */
	var $orderBy = "t_create ASC";
	
	/**
	 * Number of comments read.
	 */
	var $hasRead = null;
	
	/**
	 * @static
	 * @return string Globally unique ID for this object type
	 */
	function getObjectTypeGuid() {
		return "http://geneone.neondragon.net/objects/discussion/";
	}
	
	/**
	 * @static
	 * @return string Object type version
	 */
	function getObjectTypeVersion() {
		// Must be in PHP standardized version format.
		// See http://uk.php.net/version_compare
		return "1.0";
	}
	
	/**
	 * @static
	 * @return string Allowed Child Type GUIDs
	 */
	function getAllowedChilds() {
		return array("http://geneone.neondragon.net/objects/discussioncomment/");
	}
	
	/**
	 * @static
	 * @return string Allowed Parent Type GUIDs
	 */
	function getAllowedParents() {
		return array("http://geneone.neondragon.net/objects/forum/");
	}
	
	/**
	 * @static
	 * @return string GUIDs of other objects of which this object is compatible with.
	 */
	function getCompatibleObjectGuids() {
		return array();
	}
	
	/**
	 * Class constructor.
	 *
	 * Here we can set up the class for use. Set up any
	 * variables we might need, etc.
	 *
	 * @var array $info Object Info
	 */
	function Gene_Object_Model_Discussion($info) {
		$this->info =& $info;
		$this->objectID = $info['object_id'];
	}
	
	/**
	 * Creates a new discussion object.
	 *
	 * DO NOT use this function directly - use Gene_Object_Model::create()
	 *
	 * @static
	 *
	 * @var string $title Object Title
	 * @var string $desc Object Description
	 * @var string $content Object Contents
	 * @var object $parentobj Instance of parent object
	 * @var array $options New Object options
	 *
	 * @return mixed Returns the object ID of the newly created object
	 *               on success or a PEAR_Error object containing a
	 *               descriptive error message on failure.
	 */
	function create($title='', $desc='', $content='', &$parentobj, $options=array()) {
		$db =& Gene::getDatabase();
		$user =& Gene::getUser();
		
		if (strlen($title) < 3) {
			return PEAR::raiseError("You must specify a title of at least 3 characters.");
		}
		
		if (strlen($content) < 3) {
			return PEAR::raiseError("You must specify content of at least 3 characters.");
		}
		
		$parent = $parentobj->get();
		
		// Remove new lines
		$title = str_replace (array("\n", "\r"), '', $title);
		$desc = str_replace (array("\n", "\r"), '', $desc);
		
		$newid = $db->nextId(Gene::tableName("objects"));
		
		// Parents CSV String
		if ($parent['parentcsv'] == "-1") {
			$parents = $parent['object_id'];
		} else {
			$parents = $parent['parentcsv'].",".$parent['object_id'];
		}
		
		$objtime = time();
		
		$newobject = array(
			"object_id" => $newid,
			"type" => "discussion",
			"title" => $title,
			"description" => $desc,
			"content" => Gene_Object::cleanHtml($content),
			"owner_id" => $user->getUserId(),
			"parent" => $parent['object_id'],
			"parentcsv" => $parents,
			"t_create" => $objtime,
			"p_view" => $parent['p_view'],
			"p_edit" => "o",
			"p_revisions" => "o",
			"p_write" => $parent['p_write'],
			"p_moderate" => $parent['p_moderate'],
		);
		
		$db->extended->autoExecute(
			Gene::tableName("objects"),
			$newobject,
			MDB2_AUTOQUERY_INSERT
		);
		
		// Update parent child count
		$parentobj->bumpChildNumber();
		
		/* Revisions */
		if (!isset($options['revision_summary'])) {
			$options['revision_summary'] = "";
		}
		
		if (isset($options['revision_minor'])) {
			$options['revision_minor'] = $options['revision_minor'] ? '1' : '0';
		} else {
			$options['revision_minor'] = '0';
		}
		
		// Insert revision
		$revision = array(
			"revision_id" => $db->nextId(Gene::tableName("object_revisions")),
			"object_id" => $newid,
			"title" => $title,
			"description" => $desc,
			"content" => $content,
			"rev_summary" => $options['revision_summary'],
			"rev_minor" => $options['revision_minor'],
			"rev_time" => $objtime,
			"user_id" => $user->getUserId(),
		);
		
		$db->extended->autoExecute(
			Gene::tableName("object_revisions"),
			$revision,
			MDB2_AUTOQUERY_INSERT
		);
		
		/* Discussions Table */
		$discussion = array(
			"object_id" => $newid,
			"priority" => 1,
			"readonly" => 0,
			"last_comment_id" => $newid,
			"last_comment_time" => $objtime,
			"last_comment_user" => $user->getUserId(),
		);
		
		$db->extended->autoExecute(
			Gene::tableName("discussions"),
			$discussion,
			MDB2_AUTOQUERY_INSERT
		);
		
		/* Update Parents Discussion Last Comment */
		if ($parent['type'] == "forum") {
			$parents = array_reverse($parentobj->getParents());
			$updateids = array($parent['object_id']);
			foreach ($parents as $i) {
				if ($i['type'] == "forum") {
					$updateids[] = $i['object_id'];
				} else {
					break;
				}
			}
			
			if (count($updateids)) {
				$db->extended->autoExecute(
					Gene::tableName("discussions"),
					array(
						"last_comment_id" => $newid,
						"last_comment_time" => $objtime,
						"last_comment_user" => $user->getUserId()
					),
					MDB2_AUTOQUERY_UPDATE,
					"object_id IN (".implode(",", $updateids).")"
				);
			}
		}
		
		// Attempt to assign an alias
		$newObject =& Gene_Object::get($newid);
		$newObject->setAlias($newid.'-'.Gene_Object::generateAlias($title));
		
		/* Tags */
		if (isset($options['tags'])) {
			$newObject->updateUserTags($options['tags']);
		}
		
		$newObject->updateSearchIndex(array("taxonomy" => array($parent['object_id'] => 3)));

		return $newid;
	}
	
	/**
	 * Sets the title, description or content of the object.
	 *
	 * @var array $options Array of new content. All optional. Allowed keys:
	 *                     title - New object title
	 *                     description - New object description
	 *                     content - New object content
	 *                     revision_summary - Short summary of changes made
	 *                     revision_minor - Whether changes are minor (Boolean)
	 *
	 * @return mixed Returns true if the content has been updated,
	 *               false if nothing has updated or a PEAR_Error
	                 object if input was invalid.
	 */
	function setContent($options=array()) {
		$obj = $this->get();
		$db =& Gene::getDatabase();
		$i = array();
		
		foreach ($options as $j => $k) {
			if (in_array($j, array("title", "description", "content"))) {
				$i[$j] = $k;
			}
		}
		
		if (count($i) < 1) {
			return false;
		}
		
		// Remove new lines and check length
		if (isset($i['title'])) {
			if (strlen($i['title']) < 3) {
				return PEAR::raiseError("You must specify a title of at least 3 characters.");
			}
			
			$i['title'] = str_replace (array("\n", "\r"), '', $i['title']);
		}
		if (isset($i['description'])) {
			$i['description'] = str_replace (array("\n", "\r"), '', $i['description']);
		}
		
		// Clean Content
		if (isset($i['content'])) {
			if (strlen($i['content']) < 3) {
				return PEAR::raiseError("You must specify content of at least 3 characters.");
			}
			
			$i['content'] = Gene_Object::cleanHtml($i['content']);
		}
		
		$i['t_modify'] = time();
		
		$db->extended->autoExecute(
			Gene::tableName("objects"),
			$i,
			MDB2_AUTOQUERY_UPDATE,
			"object_id=".$db->quote((int)$obj['object_id'])
		);
		
		/* Revisions */
		if (!isset($options['revision_summary'])) {
			$options['revision_summary'] = "";
		}
		
		if (isset($options['revision_minor'])) {
			$options['revision_minor'] = $options['revision_minor'] ? '1' : '0';
		} else {
			$options['revision_minor'] = '0';
		}
		
		$title = isset($i['title']) ? $i['title'] : $obj['title'];
		$description = isset($i['description']) ? $i['description'] : $obj['description'];
		$content = isset($i['content']) ? $i['content'] : $obj['content'];

		$user =& Gene::getUser();
		
		// Insert revision
		$revision = array(
			"revision_id" => $db->nextId(Gene::tableName("object_revisions")),
			"object_id" => intval($obj['object_id']),
			"title" => $title,
			"description" => $description,
			"content" => $content,
			"rev_summary" => $options['revision_summary'],
			"rev_minor" => $options['revision_minor'],
			"rev_time" => time(),
			"user_id" => $user->getUserId(),
		);
		
		$db->extended->autoExecute(
			Gene::tableName("object_revisions"),
			$revision,
			MDB2_AUTOQUERY_INSERT
		);
		
		$this->updateSearchIndex(array("taxonomy" => array($obj['parent'] => 3)));
		
		return true;
	}
	
	/**
	 * Updates a user's taxonomies for this discussion.
	 *
	 * @var array $tags Tags
	 * @var integer $userId User ID
	 */
	function updateUserTags($tags, $userId=null) {
		$info = $this->get();
		
		if (!$userId) {
			$user =& Gene::getUser();
			$userId = $user->getUserId();
		}
		
		$t = array();
		foreach ($tags as $i) {
			$tag = preg_replace("/[^a-z0-9: ]/", "", strtolower(trim($i)));
			if ($tag) {
				$t[] = $tag;
			}
		}
		
		$tags = array_unique($t);
		
		$utaxonomy =& $this->getUserTaxonomyEntry($info['parent'], $userId);
		$utaxonomy->removeAll();
		$utaxonomy->add($tags);
		
		// Find popular tags for this discussion and stick them into taxonomy table
		$db =& Gene::getDatabase();
		$db->setLimit(10);
		$q = $db->prepare ("SELECT label, COUNT(user_id) AS users FROM ".Gene::tableName("object_user_taxonomy")." WHERE object_id=? AND set_id=? GROUP BY label ORDER BY users DESC");
		$res = $q->execute(array($info['object_id'], $info['parent']));
		$tags = $res->fetchAll();
		
		$popular = array();
		$total = 0;
		foreach ($tags as $i) {
			if ($i['users'] >= 2) { // At least 2 people must have tagged this discussion the same
				$popular[$i['label']] = $i['users'];
				$total += $i['users'];
			}
		}
		
		if (count($popular)) {
			foreach ($popular as $j => $k) {
				if ($k/$total < 0.05) { // At least 5% of people
					unset($popular[$j]);
				}
			}
		}
		
		$taxonomy =& $this->getTaxonomyEntry($info['parent']);
		$taxonomy->removeAll();
		$taxonomy->add(array_keys($popular));
	}
	
	/**
	 * Sets the sticky status of this discussion.
	 *
	 * @var boolean $sticky Whether the discussion should be sticky
	 */
	function setSticky($sticky) {
		$db =& Gene::getDatabase();
		
		if ($sticky) {
			$db->extended->autoExecute(
				Gene::tableName("discussions"),
				array("priority" => 2),
				MDB2_AUTOQUERY_UPDATE,
				"object_id=".$db->quote($this->objectID)
			);
		} else {
			$db->extended->autoExecute(
				Gene::tableName("discussions"),
				array("priority" => 1),
				MDB2_AUTOQUERY_UPDATE,
				"object_id=".$db->quote($this->objectID)
			);
		}
	}
	
	/**
	 * Sets the locked status of this discussion.
	 *
	 * @var boolean $locked Whether the discussion should be locked
	 */
	function setLocked($locked) {
		$db =& Gene::getDatabase();

		if ($locked) {
			$db->extended->autoExecute(
				Gene::tableName("discussions"),
				array(
					"readonly" => 1
				),
				MDB2_AUTOQUERY_UPDATE,
				"object_id=".$db->quote($this->objectID)
			);
			
			$db->extended->autoExecute(
				Gene::tableName("objects"),
				array(
					"p_edit" => "",
					"p_write" => ""
				),
				MDB2_AUTOQUERY_UPDATE,
				"object_id=".$db->quote($this->objectID)." OR parent=".$db->quote($this->objectID)
			);
			
			$this->info['p_edit'] = '';
			$this->info['p_write'] = '';
		} else {
			$db->extended->autoExecute(
				Gene::tableName("discussions"),
				array(
					"readonly" => 0
				),
				MDB2_AUTOQUERY_UPDATE,
				"object_id=".$db->quote($this->objectID)
			);
			
			$db->extended->autoExecute(
				Gene::tableName("objects"),
				array(
					"p_edit" => "o",
					"p_write" => "i"
				),
				MDB2_AUTOQUERY_UPDATE,
				"object_id=".$db->quote($this->objectID)." OR parent=".$db->quote($this->objectID)
			);
			
			$this->info['p_edit'] = 'o';
			$this->info['p_write'] = 'i';
		}
	}
	
	/**
	 * Returns the number of comments the user has read.
	 *
	 * @return integer Number of Comments read
	 */
	function hasRead() {
		if ($this->hasRead === null) {
			$db =& Gene::getDatabase();
			$user =& Gene::getUser();
			
			if (!$user->getUserId()) {
				return 0;
			}
		
			$member = $user->getInfo();
			
			$q = $db->prepare("SELECT comments FROM ".Gene::tableName("discussion_read")." WHERE user_id=? AND discussion_id=?");
			$res = $q->execute(array($member['user_id'], $this->objectID));
			$this->hasRead = $res->fetchOne();
		}
		
		return (int)$this->hasRead;
	}
	
	/**
	 * Mark read status of discussion.
	 *
	 * @var integer $comments Number of comments read
	 */
	function markRead($comments=0) {
		$db =& Gene::getDatabase();
		$user =& Gene::getUser();
		
		if (!$user->getUserId()) {
			return;
		}
		
		$member = $user->getInfo();
		
		if ($this->hasRead()) {
			if ($comments) {
				$db->extended->autoExecute(
					Gene::tableName("discussion_read"),
					array(
						"time" => time(),
						"comments" => $comments,
					),
					MDB2_AUTOQUERY_UPDATE,
					"user_id=".$db->quote($member['user_id'])." AND discussion_id=".$db->quote($this->objectID)
				);
			} else {
				$q = $db->prepare ("DELETE FROM ".Gene::tableName("discussion_read")." WHERE user_id=? AND discussion_id=?");
				$q->execute(array($member['user_id'], $this->objectID));
			}
		} else {
			$db->extended->autoExecute(
				Gene::tableName("discussion_read"),
				array(
					"user_id" => $member['user_id'],
					"forum_id" => $this->info['parent'],
					"discussion_id" => $this->objectID,
					"time" => time(),
					"comments" => $comments,
				),
				MDB2_AUTOQUERY_INSERT
			);
		}
	}
	
	/**
	 * Move this discussion to another forum.
	 *
	 * @var object $targetobj New Forum Object
	 * @var boolean $ghost Leave a ghost/redirect
	 * @var boolean $permission Change permissions
	 *
	 * @return mixed bool true on success
	 *               PEAR_Error on failure
	 */
	function move(&$targetobj, $ghost=true, $permissions=true) {
		$info = $this->get();
		$targetinfo = $targetobj->get();
		
		$extra = $this->getAdditionalInfo(Gene::tableName("discussions"));
		
		// If the object is already here, shut up
		if ($targetobj->objectID == $info['parent']) {
			return true;
		}
		
		$p = $this->setParent($targetobj);
		if (PEAR::isError($p)) {
			return $p;
		}
		
		$db =& Gene::getDatabase();
		
		// Update Taxonomies
		$q = $db->prepare ("UPDATE ".Gene::tableName("object_taxonomy")." SET set_id=? WHERE object_id=? AND set_id=?");
		$q->execute(array($targetinfo['object_id'], $info['object_id'], $info['parent']));
		
		// Discussion Read
		$q = $db->prepare ("UPDATE ".Gene::tableName("discussion_read")." SET forum_id=? WHERE discussion_id=?");
		$q->execute(array($targetinfo['object_id'], $info['object_id']));
		
		// Reports
		$q = $db->prepare ("UPDATE ".Gene::tableName("forum_reports")." SET forum_id=? WHERE discussion_id=?");
		$q->execute(array($targetinfo['object_id'], $info['object_id']));
		
		// Permissions
		if ($permissions) {
			$q = $db->prepare ("UPDATE ".Gene::tableName("objects")." SET p_view=?, p_write=?, p_moderate=? WHERE object_id=? OR parent=?");
			$q->execute(array($targetinfo['p_view'], $targetinfo['p_write'], $targetinfo['p_moderate'], $info['object_id'], $info['object_id']));
		}
		
		// Leave a "ghost" (redirect) in old forum
		if ($ghost) {
			$ghostid = Gene_Object::create("redirect", $info['title'], $info['description'], $info['object_id'], Gene_Object::get($info['parent']), array("type" => "internal"));
	
			$db->extended->autoExecute(
				Gene::tableName("discussions"),
				array(
					"object_id" => $ghostid,
					"priority" => 1,
					"readonly" => 1,
					"last_comment_id" => $ghostid,
					"last_comment_time" => $extra['last_comment_time'],
					"last_comment_user" => $info['owner_id'],
				),
				MDB2_AUTOQUERY_INSERT
			);
		}
		
		return true;
	}
	
	/**
	 * Returns the object ID of the forum centre this forum belongs to.
	 *
	 * @return mixed integer forum centre ID or false if this object does not
	 *               belong to a forum centre.
	 */
	function getCentreId() {
		$p = array_reverse($this->getParents());
		if (count($p)) {
			foreach ($p as $i) {
				if ($i['type'] == "forumcentre") {
					return $i['object_id'];
				} else if ($i['type'] != "forum") {
					return false;
				}
			}
		}
		
		return false;
	}
	
	/**
	 * Returns whether this forum is standalone (no forum centre)
	 *
	 * @return boolean Whether this forum is standalone
	 */
	function isStandalone() {
		if ($this->getCentreId()) {
			return false;
		} else {
			return true;
		}
	}
	
	/**
	 * Returns Gene_Object_Forum_Report for this object
	 */
	function &getReport() {
		require_once ("Gene/Object/Forum/Report.php");
		$r =& new Gene_Object_Forum_Report($this);
		return $r;
	}
}
 
?>